Skip to content

feat(objc): Add SentryObjC wrapper SDK#7598

Open
philprime wants to merge 146 commits into
mainfrom
philprime/objc-wrapper-sdk-6342
Open

feat(objc): Add SentryObjC wrapper SDK#7598
philprime wants to merge 146 commits into
mainfrom
philprime/objc-wrapper-sdk-6342

Conversation

@philprime
Copy link
Copy Markdown
Member

@philprime philprime commented Mar 4, 2026

📜 Description

Adds a pure Objective-C SDK wrapper (SentryObjC) that enables Sentry usage in Objective-C++ projects with CLANG_ENABLE_MODULES=NO. This solves the long-standing issue where Swift SDK APIs are inaccessible without Clang modules, affecting React Native, Haxe, and other custom build systems.

Read the develop-docs/SENTRY-OBJC.md architecture document first.

💡 Motivation and Context

Closes #6342

The Problem

Since SDK 8.54.0, the Swift SDK's public API requires Clang modules to be imported. When CLANG_ENABLE_MODULES=NO:

  • #import <Sentry/Sentry.h> only exposes Objective-C APIs
  • Swift types like SentrySDK, SentryOptions.sessionReplay, and the Metrics API are unavailable
  • Build errors: use of undeclared identifier 'SentrySDK'

This breaks projects that cannot enable modules, particularly:

  • React Native (uses ObjC++ extensively)
  • Haxe-based applications
  • Custom build systems with strict compiler settings

Implementation Approach & Thought Process

Initial Considerations

Option 1: Make Swift SDK importable without modules

  • ❌ Extremely difficult - would require rewriting core SDK architecture
  • ❌ Breaks existing Swift API design patterns
  • ❌ Not backwards compatible

Option 2: Pure ObjC wrapper (chosen approach)

  • ✅ Preserves existing SDK architecture
  • ✅ Allows Swift and ObjC to coexist
  • ✅ Minimal maintenance burden
  • ✅ Clean separation of concerns

Architecture Decision

The wrapper uses a three-layer architecture:

┌─────────────────────────────────────┐
│   SentryObjC (Pure ObjC Headers)    │  ← Public API for no-modules context
│  Sources/SentryObjC/Public/*.h      │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│   SentryObjCBridge (Swift)          │  ← Forwards to Swift SDK
│  Sources/SentryObjCBridge/*.swift   │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│     SentrySwift (Main SDK)          │  ← Actual implementation
└─────────────────────────────────────┘

Why this design?

See develop-docs/SENTRY-OBJC.md

📝 Checklist

  • I added tests to verify the changes
  • No new PII added
  • Docs updated (sample README, AGENTS.md)
  • Wizard update (not applicable - advanced use case)
  • Review from native team
  • No breaking changes
  • No breaking changes for hybrid SDKs

🔗 Related Issues & Context

Add a pure Objective-C SDK wrapper that mirrors the Sentry public API so it can be used from ObjC++ without modules (e.g. for React Native, Haxe, custom build systems). Includes:

- SentryObjC product and target in Package.swift
- Re-declared headers for all public types in Sources/SentryObjC/Public/
- Wrapper implementations for Swift-only types (Unit, Metric, MetricValue, AttributeContent, RedactRegionType)
- iOS-ObjectiveCpp-NoModules sample using SentryObjC
- sdk_objc_api.json generation via extract-objc-api.py for API stability CI
- Makefile targets: build-spm-objc, verify-objc, generate-objc-api

Refs GH-6342
@philprime philprime self-assigned this Mar 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 4, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (objc) Add SentryObjC wrapper SDK by philprime in #7598

Documentation 📚

  • Add SentryCrash analysis and improvement plan document by itaybre in #7528

Internal Changes 🔧

  • (deps) Bump json from 2.18.1 to 2.19.2 by dependabot in #7709

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 4, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 975a1b4

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 38.18182% with 136 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.167%. Comparing base (7d833a3) to head (975a1b4).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
Sources/SentryObjCBridge/SentryObjCBridge.swift 0.000% 100 Missing ⚠️
...entryObjCTypes/Public/SentryObjCAttributeContent.m 12.500% 28 Missing ⚠️
...s/SentryObjCBridge/SentryObjCBridgeCallbacks.swift 50.000% 4 Missing ⚠️
...ources/Swift/Protocol/SentryAttributeContent.swift 0.000% 2 Missing ⚠️
Sources/SentryObjCTypes/Public/SentryObjCMetric.m 90.000% 1 Missing ⚠️
Sources/SentryObjCTypes/Public/SentryObjCUnit.m 0.000% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #7598       +/-   ##
=============================================
- Coverage   85.518%   85.167%   -0.352%     
=============================================
  Files          487       494        +7     
  Lines        29659     29880      +221     
  Branches     12896     12889        -7     
=============================================
+ Hits         25364     25448       +84     
- Misses        4244      4382      +138     
+ Partials        51        50        -1     
Files with missing lines Coverage Δ
...ryObjCBridge/SentryObjCBridgeTypeConversions.swift 100.000% <100.000%> (ø)
...ces/SentryObjCTypes/Public/SentryObjCMetricValue.m 100.000% <100.000%> (ø)
Sources/Swift/Core/Helper/Log/SentryLevel.swift 100.000% <ø> (ø)
...tegrations/SessionReplay/SentryReplayOptions.swift 97.175% <ø> (ø)
...ift/Integrations/UserFeedback/SentryFeedback.swift 59.183% <ø> (ø)
Sources/Swift/Protocol/SentryAttribute.swift 92.361% <ø> (ø)
Sources/Swift/Protocol/SentryLog.swift 97.777% <ø> (ø)
Sources/Swift/Protocol/SentryMetric.swift 100.000% <ø> (ø)
Sources/Swift/SentryExperimentalOptions.swift 75.000% <ø> (ø)
Sources/Swift/Tools/SentryEnvelope.swift 83.333% <ø> (ø)
... and 9 more

... and 11 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7d833a3...975a1b4. Read the comment docs.

Add detailed documentation to core SentryObjC public APIs following
Objective-C documentation best practices:
- Document all properties with their purpose and behavior
- Document all methods with parameters and return values
- Add class-level documentation explaining usage context
- Include notes about automatic behavior and warnings where applicable

Files documented:
- SentryObjCSDK: Main SDK entry point
- SentryObjCOptions: Configuration options
- SentryObjCEvent: Event data structure
- SentryObjCScope: Contextual data container
- SentryObjCBreadcrumb: Breadcrumb trail
- SentryObjCUser: User identification
- SentryObjCSpanProtocol: Performance tracing protocol

Also adds changelog entry explaining the purpose of the SentryObjC
wrapper SDK.
Add comprehensive documentation to exception, attachment, message,
and tracing-related classes:
- SentryObjCException: Exception information
- SentryObjCAttachment: File attachments
- SentryObjCMessage: Log messages
- SentryObjCSpanContext: Span trace context
- SentryObjCTransactionContext: Transaction context

All classes now include detailed property and method documentation
following Objective-C best practices.
Add comprehensive documentation to:
- SentryObjCFrame: Stack frame with source location and context
- SentryObjCStacktrace: Stack trace with frames and registers
- SentryObjCThread: Thread information and crash state
- SentryObjCMechanism: Error mechanism and handling context
- SentryObjCDebugMeta: Debug symbols and binary metadata
- SentryObjCRequest: HTTP request information
- SentryObjCGeo: Geographical location data

All classes now include detailed property and method documentation.
Add comprehensive documentation to:
- SentryObjCReplayOptions: Session replay configuration with privacy controls
- SentryObjCReplayApi: Runtime replay control and masking API
- SentryObjCSamplingContext: Context for dynamic trace sampling decisions

All properties and methods now include detailed documentation explaining
their purpose, behavior, and usage.
Remove the conditional check for sdk_objc_api.json existence since an
empty baseline file will be added to main in a follow-up PR. This
simplifies the workflow logic.
Keep the original problem description explaining why modules don't work
in ObjC++ projects, then show how SentryObjC solves this issue. This
provides better context for readers to understand why SentryObjC exists.

- Restore "Problem" section describing module import failures
- Add "Solution: SentryObjC" section explaining how it solves the issue
- List key differences from the main Sentry framework
- Reference original issue #4543 and solution PR #6342
Sync with main to include latest changes and adopt new sample
structure using projectReferences instead of SPM packages.
philprime and others added 6 commits March 10, 2026 12:49
Replace Python-based regex parsing with clang AST dump and jq
processing. The new approach:

- Uses xcrun clang -ast-dump=json for reliable parsing
- Extracts declarations via jq queries on intermediate files
- Outputs structured JSON objects instead of string signatures
- Migrates from Python to pure bash/jq pipeline

Also standardize filename to sdk_api_objc.json to match the
sdk_api.json and sdk_api_sentryswiftui.json naming convention.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Include the iOS-ObjectiveCpp-NoModules sample in CI build verification
to ensure it continues to build successfully with the SentryObjC wrapper.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add the SentryObjC product and target to Package@swift-6.1.swift so
the sample can reference it. Also revert iOS-ObjectiveCpp-NoModules
sample to use SPM packages instead of projectReferences since
SentryObjC is an SPM product, not an Xcode project target.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Complete documentation for:
- SentryObjCAppStartMeasurement (app start types and timestamps)
- SentryObjCAttributeContent (typed attribute values)
- SentryObjCBaggage (distributed tracing propagation)
- SentryObjCError (SDK error codes and helpers)
- SentryObjCLogger (structured logging interface)
- SentryObjCMechanismContext (crash metadata)
- SentryObjCMetric (custom performance metrics)
- SentryObjCMetricValue (metric value types)
- SentryObjCNSError (serializable error representation)

All public types, properties, methods, and enum values now have
comprehensive documentation following Objective-C best practices.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@philprime
Copy link
Copy Markdown
Member Author

@sentry review

@philprime philprime added the ready-to-merge Use this label to trigger all PR workflows label Mar 10, 2026
@sentry
Copy link
Copy Markdown

sentry Bot commented Mar 10, 2026

📲 Install Builds

iOS

🔗 App Name App ID Version Configuration
SDK-Size io.sentry.sample.SDK-Size 9.13.0 (1) Release

⚙️ sentry-cocoa Build Distribution Settings

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 10, 2026

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1224.15 ms 1259.33 ms 35.18 ms
Size 24.14 KiB 1.16 MiB 1.13 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
7814719 1220.98 ms 1254.14 ms 33.16 ms
eddca8a 1226.17 ms 1259.98 ms 33.81 ms
85ee155 1227.40 ms 1251.52 ms 24.12 ms
a781a3d 1210.98 ms 1244.31 ms 33.33 ms
c67ced3 1223.22 ms 1256.15 ms 32.93 ms
29d546e 1224.06 ms 1257.05 ms 32.98 ms
bdd8e0e 1233.35 ms 1266.96 ms 33.60 ms
1155f0c 1222.41 ms 1251.18 ms 28.76 ms
7d833a3 1226.72 ms 1263.42 ms 36.70 ms
86bd73c 1232.52 ms 1269.53 ms 37.01 ms

App size

Revision Plain With Sentry Diff
7814719 24.14 KiB 1.15 MiB 1.13 MiB
eddca8a 24.14 KiB 1.15 MiB 1.13 MiB
85ee155 24.14 KiB 1.16 MiB 1.13 MiB
a781a3d 24.14 KiB 1.16 MiB 1.13 MiB
c67ced3 24.14 KiB 1.16 MiB 1.13 MiB
29d546e 24.14 KiB 1.15 MiB 1.13 MiB
bdd8e0e 24.14 KiB 1.16 MiB 1.13 MiB
1155f0c 24.14 KiB 1.16 MiB 1.13 MiB
7d833a3 24.14 KiB 1.16 MiB 1.13 MiB
86bd73c 24.14 KiB 1.16 MiB 1.13 MiB

Previous results on branch: philprime/objc-wrapper-sdk-6342

Startup times

Revision Plain With Sentry Diff
fd98cd2 1221.76 ms 1255.34 ms 33.58 ms
39908fa 1216.45 ms 1247.67 ms 31.22 ms
1db3ef0 3762.07 ms 3780.62 ms 18.56 ms
f042c2d 1217.49 ms 1232.57 ms 15.08 ms
a1c7a37 1218.20 ms 1247.48 ms 29.28 ms
3d2e8a4 1235.17 ms 1264.59 ms 29.42 ms
14dc1d7 1207.65 ms 1228.72 ms 21.08 ms
a1f5242 1222.28 ms 1245.53 ms 23.25 ms
31709d3 1223.00 ms 1255.70 ms 32.70 ms
694c3ad 1225.69 ms 1250.48 ms 24.79 ms

App size

Revision Plain With Sentry Diff
fd98cd2 24.14 KiB 1.16 MiB 1.13 MiB
39908fa 24.14 KiB 1.13 MiB 1.11 MiB
1db3ef0 24.14 KiB 1.12 MiB 1.10 MiB
f042c2d 24.14 KiB 1.12 MiB 1.10 MiB
a1c7a37 24.14 KiB 1.12 MiB 1.10 MiB
3d2e8a4 24.14 KiB 1.15 MiB 1.13 MiB
14dc1d7 24.14 KiB 1.13 MiB 1.11 MiB
a1f5242 24.14 KiB 1.12 MiB 1.10 MiB
31709d3 24.14 KiB 1.15 MiB 1.13 MiB
694c3ad 24.14 KiB 1.15 MiB 1.13 MiB

philprime and others added 2 commits March 10, 2026 14:05
The SentryObjC product depends on SentryCppHelper, but it was only
defined in the binary targets section. Add it to the compile-from-source
targets array so it's available when building SentryObjC from source.

Fixes SPM package resolution error:
target 'SentryCppHelper' referenced in product 'SentryObjC' could not be found

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add SentryObjCSDK.m implementation that forwards all method calls to
SentrySDKInternal. This enables SentryObjC to work in projects with
CLANG_ENABLE_MODULES=NO, where Swift's @objc bridging is unavailable.

The wrapper provides a pure Objective-C implementation of the SentrySDK
class, making all SDK methods accessible via #import <SentryObjC.h>
without requiring modules or Swift bridging.

Update iOS-ObjectiveCpp-NoModules sample to use the corrected import
style (#import <SentryObjC.h> instead of <SentryObjC/SentryObjC.h>)
and add module.modulemap for SPM module resolution.

Refs #6342
@sentry
Copy link
Copy Markdown

sentry Bot commented Mar 10, 2026

Sentry Build Distribution

App Version Configuration
App 9.6.0 (1) Release

SentryCppHelper must remain in the initial targets array because the
binary distribution products (Sentry, SentrySwiftUI, etc.) depend on it.

Moving it to the compile-from-source section broke those binary products.
The target can be safely referenced by both binary and source products.
@philprime philprime force-pushed the philprime/objc-wrapper-sdk-6342 branch from 7abca39 to 913cdfd Compare March 10, 2026 13:37
@sentry
Copy link
Copy Markdown

sentry Bot commented Mar 10, 2026

Sentry Build Distribution

App Version Configuration
App 9.6.0 (1) Release

Keep unbeautified xcodebuild output in slice job logs.

Print archive diagnostics for each produced slice so successful producer jobs expose emitted architectures and Swift module files.
philprime added 2 commits May 7, 2026 16:23
Archive macOS slices with a generic destination so Xcode builds all valid architectures instead of selecting the runner's My Mac arch.

Make standalone SentryObjC cleanup idempotent after removing its temp directory during the slice loop.
Log effective xcodebuild settings before each slice build and print final archive metadata after the slice is produced.

Build xcodebuild commands from arrays so optional destination args do not trip nounset when they are empty.
Comment thread .github/workflows/assemble-xcframework-sentryobjc.yml
philprime added 6 commits May 8, 2026 10:44
…r-sdk-6342

# Conflicts:
#	agents.toml
#	scripts/build-xcframework-slice.sh
#	scripts/compress-xcframework.sh
Add "Get version" step matching the variant workflow so
the release-version input is consumed into VERSION env.
The merge conflict resolution dropped the destination_args
that ensure macOS slices build for all architectures.
Avoids unbound variable error with empty bash arrays
under set -u on macOS system bash.
Revert to the working version from before the merge
with origin/main. Uses array-based xcodebuild args
which avoids the empty array unbound variable issue.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 18e1f5f. Configure here.

Comment thread Sources/SentryObjCBridge/SentryObjCBridgeCallbacks.swift
Combine main's logging/structure improvements with
array-based xcodebuild args from this branch.
}
}

#if canImport(UIKit) && !os(visionOS)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The Swift conditional compilation guard is missing the !SENTRY_NO_UI_FRAMEWORK check, causing it to diverge from the corresponding Objective-C protocol guard, which can lead to build failures.
Severity: MEDIUM

Suggested Fix

Align the Swift conditional compilation guard with the Objective-C guard. Update the guard in SentryObjCBridgeCallbacks.swift to include a check for !SENTRY_NO_UI_FRAMEWORK, making it consistent with the protocol declaration in SentryObjCBridging.h.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: Sources/SentryObjCBridge/SentryObjCBridgeCallbacks.swift#L23

Potential issue: There is a mismatch between the conditional compilation guards for
session replay callbacks in Swift and Objective-C. The Swift code is guarded by `#if
canImport(UIKit) && !os(visionOS)`, while the Objective-C protocol declaration is
guarded by `#if (TARGET_OS_IOS || TARGET_OS_TV) && !SENTRY_NO_UI_FRAMEWORK`. When
building with the `SENTRY_NO_UI_FRAMEWORK` flag on iOS or tvOS, the Swift bridge will
compile methods that are not declared in the Objective-C protocol. This violates the
bridging contract, can cause protocol conformance build failures in Xcode, and results
in dead code being included in the final binary.

Also affects:

  • Sources/SentryObjCTypes/Public/SentryObjCBridging.h:159~162
  • Sources/SentryObjC/SentrySDKCallbackBridge.m:34~38
  • Sources/SentryObjC/Public/SentryObjCDefines.h:50~52


static const void *kBeforeSendMetricKey = &kBeforeSendMetricKey;

@implementation SentryOptions (ObjCBridge)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: To make it obvious this file is a category extension, maybe it's worth renaming this file to SentryOptions+ObjCBridge.m? (if you agree, SentryReplayObjCBridge.m could also be renamed)

Copy link
Copy Markdown
Contributor

@noahsmartin noahsmartin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to jump in with little context on this history of this PR, but as the author of the issue this is aimed at fixing I felt like I had to provide some context...

As written the PR doesn't really implement what was proposed in the issue. The referenced issue (#6342) shows the objc version of the SDK starts with import Sentry. That's because it's proposing a new SDK that wraps the existing Sentry SDK. What this PR does though is something quite different, it introduces a new target that compiles the same source files as the current SDK but with different settings. IMO this is going to be much more difficult to maintain than what I proposed, and in particular make it harder to migrate more of the SDK to Swift.

An ObjC wrapper SDK would involve 0 changes to existing code files (objc and swift files) and just add one new target to the Package.swift file (SentryObjC - with a dependency on SentrySPM). I would really encourage trying this option since it will make long term maintenance much easier. This PR entangles the two distributions (swift public API and ObjC API) and my suggestion separates these two concerns into distinct layers where the ObjC API builds on the Swift API. A layered approach like that with a clear boundary between the two is usually easier to work with than try to support two modes like this PR is doing.

@philprime
Copy link
Copy Markdown
Member Author

Thanks for the feedback @noahsmartin, I hope I can give more context like this.

As written the PR doesn't really implement what was proposed in the issue.

I believe you and me have to different takes on the whole topic of building an Objective-C SDK.

The goal of this PR is introducing an Objective-C SDK which wraps our existing Swift+ObjC SDK, to make our Swift-only features available to ObjC too and so we eventually can do breaking changes when turning it into a mostly-Swift SDK.

By asking SDK users to switch from Sentry to SentryObjC they can continue using the SDK from Objective-C, while we can do breaking-change migration, e.g. drop @objc in our Swift+ObjC SDK further down the line (in major releases).

As Objective-C is the past and Swift is the future, my approach sets focus on refactoring the main SDK to Swift, but still offering an (backwards-compatibility) adapter for Objective-C++ projects.

The referenced issue (#6342) shows the objc version of the SDK starts with import Sentry.

I believe this was already discovered and discussed in this PR comment.

I originally built this wrapper SDK to use the same naming too, so it's a direct drop-in replacement. I assumed that the embedded Sentry.framework would be internal only, but both export a class with the same ObjC runtime name which could cause duplicate class conflicts at runtime:

  • SentryObjC.framework/SentryObjC: _OBJC_CLASS_$_SentrySDK is the ObjC class from SentrySDK.m
  • SentryObjC.framework/Frameworks/Sentry.framework/Sentry: _OBJC_CLASS_$__TtC6Sentry9SentrySDK is the Swift class, mangled symbol, but registers as SentrySDK in the ObjC runtime via @objc

Since SentryObjC is the new SDK, I renamed its class to SentryObjcSDK to avoid the collision.

What this PR does though is something quite different, it introduces a new target that compiles the same source files as the current SDK but with different settings.

This claim is only partially true, because it introduces three new targets (SentryObjC, SentryObjCBridge, SentryObjCTypes) but each with their own files, and it only reuses some of the public headers of the base SDK to deduplicate code.

What the PR actually does is create a pure Objective-C wrapper layer around the existing Swift SDK. The new code acts as a facade/bridge so that ObjC/ObjC++ consumers who can't use Clang modules can still access the full SDK API. The new targets depend on the existing SDK but don't recompile it, they call into it via @objc bridge methods and re-export its existing pure-ObjC headers.

IMO this is going to be much more difficult to maintain than what I proposed, and in particular make it harder to migrate more of the SDK to Swift.

The maintenance overhead is real, I agree with you there, especially with the hand-written ObjC headers that need to stay in sync with the Swift SDK. But the alternative proposal would face the same bridging complexity for any Swift-only type (structs, enums with associated values, generics). The Metrics API, Logger API, and SentryAttributeContent all require manual ObjC-compatible wrappers regardless of architecture.

Regarding making it harder to migrate, I must disagree, as my approach decouples ObjC consumers from the internal SDK implementation as described above. If we migrate an internal ObjC type to a Swift struct or enum, only the bridge mapping code needs updating, the consumer-facing ObjC headers don't change.

An ObjC wrapper SDK would involve 0 changes to existing code files (objc and swift files) and just add one new target to the Package.swift file (SentryObjC - with a dependency on SentrySPM).

I have to disagree. The Metrics API is written in pure Swift using structs and generics. It is not compatible with Objective-C and will never be, hence the bridging logic which introduces an Objective-C compatible API. This is the main reason why we are building an Objective-C SDK right now, because sentry-unreal needs it.

I would really encourage trying this option since it will make long term maintenance much easier. This PR entangles the two distributions (swift public API and ObjC API) and my suggestion separates these two concerns into distinct layers where the ObjC API builds on the Swift API.

I don't believe this is is correct with regards to this pull request.

We are only reusing the public ObjC headers to reduce code duplication. We use @objc(SentryX) annotations to pin the ObjC runtime name so that the hand-written headers in Sentry resolve correctly at link time. They don't constraint future Swift refactoring as we can rename them in Swift and migrate to the ObjC SDK if necessary.

A layered approach like that with a clear boundary between the two is usually easier to work with than try to support two modes like this PR is doing.

My architecture is also creating two distinct layers (with a helper layer in-between): an outer Objective-C SDK wrapping a bridging layer wrapping the Swift(+ObjC) SDK. The boundary is enforced at the module level when SentryObjCBridge imports the SDK as an external dependency, and SentryObjC's .m files only see the bridge through a typed forward declaration. Changes to SDK internals that don't affect the @objc-exposed surface do not produce changes in the wrapper layer. It is not two modes of the same SDK.

I hope this clear things up and that this is not blocking a soonish merge and release.

Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md
@github-actions
Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code can easily blow up and is hard to test. We had severe bugs in the past. Be extra careful when changing these files, and have an extra careful look at these:

  • .github/file-filters.yml

Comment on lines +212 to +215
+ (SentryFeedbackAPI *)feedback
{
return [SentryObjCBridge sdkFeedback];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The sdkFeedback property is missing from the SentryObjCBridging protocol, bypassing the intended contract verification between the Objective-C and Swift bridge.
Severity: LOW

Suggested Fix

Add the sdkFeedback class property to the SentryObjCBridging protocol in SentryObjCBridging.h, enclosed in the same preprocessor guards (#if TARGET_OS_IOS && SENTRY_HAS_UIKIT) used elsewhere. This will make the protocol accurately reflect the bridging API and allow the compiler to enforce conformance.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: Sources/SentryObjC/SentryObjCSDK.m#L212-L215

Potential issue: The `SentryObjCBridging.h` protocol, which is intended to be the single
source of truth for the Objective-C to Swift bridge, is missing the declaration for the
`sdkFeedback` property. The Objective-C code in `SentryObjCSDK.m` currently relies on a
forward-declaration to access the Swift implementation. This bypasses the protocol's
contract verification, meaning the compiler does not check for the presence of
`sdkFeedback` via protocol conformance. This creates a maintenance risk and allows for
potential drift between the Objective-C call sites and the Swift implementation,
violating the documented architectural pattern.

Also affects:

  • Sources/SentryObjCTypes/Public/SentryObjCBridging.h:1~172
  • Sources/SentryObjCBridge/SentryObjCBridge.swift:289~293

@noahsmartin
Copy link
Copy Markdown
Contributor

Thanks @philprime I believe we are in alignment with all the technical points it's just the public API and some subjective decisions around architecture that we don't have the same opinions on. Concretely here is how I see the differences in our approach:

My proposal is to have a wrapper SDK that only exposes an objc API. The architecture would be like this:

┌─────────────────────────────────────────────┐
│ SentryObjC                                  │
│                                             │
│ Public interface: Objective-C headers only  │
└──────────────────────┬──────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│ SentryObjCCompat                            │
│                                             │
│ Public interface: Swift module              │
│                 + generated ObjC header     │
└──────────────────────┬──────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────┐
│ Sentry                                     │
│                                             │
│ Public interface: Swift module only         │
└─────────────────────────────────────────────┘

In reality the Sentry target currently contains a mix of ObjC and Swift but the other two would be added and we would slowly move towards Sentry being unencumbered by ObjC code (metrics is an example of this).
Even more concretely, my proposal is to add a new library to the Package.swift like this:

.library(name: "SentryObjC", targets: ["SentryObjC"])

And targets like this:

    .target(
        name: "SentryObjCCompat",
        dependencies: ["SentrySwift"],
        path: "Sources/SentryObjCCompat",
        publicHeadersPath: "Public",
    .target(
        name: "SentryObjC",
        dependencies: ["SentryObjCCompat"],
        path: "Sources/SentryObjC",
        publicHeadersPath: "Public",
    )

Your proposal does not include a "wrapper" SDK in the traditional sense. It does not wrap the existing SDK, it creates a new library that includes the existing targets (seen in your changes to Package.swift). It also would create a new "mode" of compiling the SDK where the module name would be changed to SentryObjC and this SentryObjC would include both public Swift and ObjC code. You can see this by your changes to the files in Sentry/Public, which now need different imports because the same file is being compiled with different names.This is a materially different public API than the ObjC only wrapper I am proposing.

These are different approaches to solve the same problem. However, I would suggest that the method I'm proposing would be easier to understand (in fact it sounds a lot like what you proposed in the PR description), easier to maintain, and easier for users. The key technical differences would be that my proposal is entirely a wrapper - no changes would be made to existing source files. You could validate this by making my proposal in an entirely new GitHub repo, the resulting framework would be identical to the one you'd make if you implemented my proposal in this repo. I am not proposing a new repo (too much maintenance burden). Rather just a thought experiment to demonstrate some clear separation of concerns. This is why I believe (subjectively) that my proposal would be easier, it provides clear boundaries between systems which generally makes it easier to work with the codebase.

I would defer to @itaybre and @philprime to evaluate the subjective pros/cons of these approaches. All I ask that if we go with the library that provides both objc and swift targets in one, we keep #6342 open as a feature request to provide a target that only has publicly visible objc code, with no swift module and no auto-generated objc code from swift @objc annotations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create an ObjC SDK

5 participants